package fi.saarinen.simulator;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import fi.saarinen.simulator.instructions.AddAddX;
import fi.saarinen.simulator.instructions.AddQSubQSccDBcc;
import fi.saarinen.simulator.instructions.AndMulABCDExg;
import fi.saarinen.simulator.instructions.Bcc;
import fi.saarinen.simulator.instructions.CmpEor;
import fi.saarinen.simulator.instructions.Miscellaneous;
import fi.saarinen.simulator.instructions.MoveByte;
import fi.saarinen.simulator.instructions.MoveLong;
import fi.saarinen.simulator.instructions.MoveQ;
import fi.saarinen.simulator.instructions.MoveWord;
import fi.saarinen.simulator.instructions.OrDivSBCD;
import fi.saarinen.simulator.instructions.ShiftRotate;
import fi.saarinen.simulator.instructions.SubSubX;

/**
 * Represents the state of the simulated M68000.
 * 
 * @author jsaarinen
 */
public class M68000Context
{
  /**
   * Eight 32-bit wide data registers.
   */
  private int[] dataRegisters;
  
  /**
   * Eights 32-bit wide address registers.
   * Though only low 24 bits is used in real M68000.
   */
  private int[] addressRegisters;
  
  /**
   * Points to the code that is currently executed.
   */
  private int programCounter;
  
  /**
   * RAM.
   */
  private byte[] RAM;
  
  /**
   * Code.
   */
  private byte[] programCode;

  private short statusRegister;
  
  private final static short CARRY_FLAG     = 0x0001;
  private final static short OVERFLOW_FLAG  = 0x0002;
  private final static short ZERO_FLAG      = 0x0004;
  private final static short NEGATIVE_FLAG  = 0x0008;
  private final static short XTEND_FLAG     = 0x0010;
  
  private final static Map<Short, Instruction> instructions = new HashMap<Short, Instruction>();
  static
  {
    instructions.put((short) 0x0000, null);
    instructions.put((short) 0x1000, new MoveByte());
    instructions.put((short) 0x2000, new MoveLong());
    instructions.put((short) 0x3000, new MoveWord());
    instructions.put((short) 0x4000, new Miscellaneous());
    instructions.put((short) 0x5000, new AddQSubQSccDBcc());
    instructions.put((short) 0x6000, new Bcc());
    instructions.put((short) 0x7000, new MoveQ());
    instructions.put((short) 0x8000, new OrDivSBCD());
    instructions.put((short) 0x9000, new SubSubX());
    instructions.put((short) 0xA000, null);
    instructions.put((short) 0xB000, new CmpEor());
    instructions.put((short) 0xC000, new AndMulABCDExg());
    instructions.put((short) 0xD000, new AddAddX());
    instructions.put((short) 0xE000, new ShiftRotate());
    instructions.put((short) 0xF000, null);
  }
  
  /**
   * Initializes code and memory.
   * 
   * @param memorySizeKB
   *  How much RAM in kilobytes the machine has.
   *  
   * @param programCode
   *  Program code to be executed.
   */
  public M68000Context(int memorySizeKB, byte[] programCode)
  {
    this.dataRegisters = new int[8];
    for (int i = 0; i < this.dataRegisters.length; i++)
    {
      this.dataRegisters[i] = 0;
    }
    this.addressRegisters = new int[8];
    for (int i = 0; i < this.addressRegisters.length; i++)
    {
      this.addressRegisters[i] = 0;
    }
    this.programCounter = 0;
    this.RAM = new byte[memorySizeKB * 1024];
    this.programCode = programCode;
    this.statusRegister = 0;
  }
  
  public void setZeroFlag(boolean bit)
  {
    if (bit)
    {
      this.statusRegister |= ZERO_FLAG;
    }
    else
    {
      this.statusRegister &= ~ZERO_FLAG;
    }
  }
  
  public void setCarryFlag(boolean bit)
  {
    if (bit)
    {
      this.statusRegister |= CARRY_FLAG;
    }
    else
    {
      this.statusRegister &= ~CARRY_FLAG;
    }
  }
  
  public void setOverflowFlag(boolean bit)
  {
    if (bit)
    {
      this.statusRegister |= OVERFLOW_FLAG;
    }
    else
    {
      this.statusRegister &= ~OVERFLOW_FLAG;
    }
  }
  
  public void setExtendFlag(boolean bit)
  {
    if (bit)
    {
      this.statusRegister |= XTEND_FLAG;
    }
    else
    {
      this.statusRegister &= ~XTEND_FLAG;
    }
  }
  
  public void setNegativeFlag(boolean bit)
  {
    if (bit)
    {
      this.statusRegister |= NEGATIVE_FLAG;
    }
    else
    {
      this.statusRegister &= ~NEGATIVE_FLAG;
    }
  }

  public int getProgramCounter()
  {
    return this.programCounter;
  }

  public void setProgramCounter(int programCounter)
  {
    if (programCounter >= this.programCode.length || programCounter < 0)
    {
      throw new IllegalArgumentException("Invalid PC value!");
    }
    this.programCounter = programCounter;
  }

  /**
   * Returns the next instruction. 
   * 
   * @return
   *  null when instruction stream is finished.
   */
  public Instruction getNextInstruction()
  {
    if (this.programCounter >= this.programCode.length)
    {
      return null;
    }
    int opcode = getNextWord();
    Instruction instruction = instructions.get(opcode);
    if (instruction == null)
    {
      throw new RuntimeException("Invalid opcode: " + Integer.toHexString(opcode));
    }
    return instruction;
  }

  public int getNextWord()
  {
    int hiByte = this.programCode[this.programCounter + 0] & 0x000000FF;
    int loByte = this.programCode[this.programCounter + 1] & 0x000000FF;
    this.programCounter += 2;
    int word = (hiByte << 8) | loByte;
    return word;
  }
  
  @Override
  public String toString()
  {
    return "M68000Context [dataRegisters="
        + Arrays.toString(this.dataRegisters) + ", addressRegisters="
        + Arrays.toString(this.addressRegisters) + ", programCounter="
        + this.programCounter + ", statusRegister=" + this.statusRegister + "]";
  }
}
